//package com.example.demo.ziliao;
//
//import javax.swing.tree.TreeNode;
//import java.io.Serializable;
//import java.util.AbstractMap;
//import java.util.Map;
//import java.util.Set;
//
//public class HashMap<K,V> extends AbstractMap<K,V>
//        implements Map<K,V>, Cloneable, Serializable {
//
//    private static final long serialVersionUID = 362498820763181265L;
//    /**
//     * The default initial capacity - MUST be a power of two.
//     */
//
//    //HashMap的初始容量为16，HashMap的容量指的是存储元素的数组大小，即桶的数量
//    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//
//    /**
//     * The maximum capacity, used if a higher value is implicitly specified
//     * by either of the constructors with arguments.
//     * MUST be a power of two <= 1<<30.
//     */
//
//    //HashMap的最大的容量
//    static final int MAXIMUM_CAPACITY = 1 << 30;
//
//    /**
//     * The load factor used when none specified in constructor.
//     */
//
//    /**
//     * DEFAULT_LOAD_FACTOR:HashMap的负载因子，影响HashMap性能的参数之一，是时间和空间之间的权
//     * 衡，后面会看到HashMap的元素存储在Node数组中，这个数组的大小这里称为“桶”的大小.
//     * 另外还有一个参数size指的是我们往HashMap中put了多少个元素。
//     * 当size==桶的数量*DEFAULT_LOAD_FACTOR的时候，这时HashMap要进行扩容操作，也就是桶不能装
//     * 满。DEFAULT_LOAD_FACTOR是衡量桶的利用率:DEFAULT_LOAD_FACTOR较小时（桶的利用率较小）,
//     * 这时浪费的空间较多（因为只能存储桶的数量*DEFAULT_LOAD_FACTOR个元素，超过了就要进行扩容）,
//     * 这种情况下往HashMap中put元素时发生冲突的概率也很小，所谓冲突指的是：多个元素被put到了同一个桶 中;
//     * 冲突小时（可以认为一个桶中只有一个元素）put、get等HashMap的操作代价就很低，可以认为是O(1);
//     * 桶的利用率较大的时候(DEFAULT_LOAD_FACTOR很大，注意可以大于1，因为冲突的元素是使用链表或者 红黑树连接起来的)
//     * 空间利用率较高，
//     * 这也意味着一个桶中存储了很多元素，这时HashMap的put、get等操作代价就相对较大，因为每一个put或get操作都变成了
//     * 对链表或者红黑树的操作，代价肯定大于O(1),所以说DEFAULT_LOAD_FACTOR是空间和时间的一个平衡点;
//     * DEFAULT_LOAD_FACTOR较小时，需要的空间较大，但是put和get的代价较小;
//     * DEFAULT_LOAD_FACTOR较大时，需要的空间较小，但是put和get的代价较大）。
//     * <p>
//     * 扩容操作就是把桶的数量*2，即把Node数组的大小调整为扩容前的2倍，至于为什么是两倍，分析扩容函 数时会讲解，
//     * 这其实是一个trick。Node数组中每一个桶中存储的是Node链表，当链表长度>=8的时候，
//     * 链表会变为红黑树结构（因为红黑树的增删改查复杂度是logn，链表是n，红黑树结构比链表代价更小）。
//     */
//    static final float DEFAULT_LOAD_FACTOR = 0.75f;
//
//    /**
//     * The bin count threshold for using a tree rather than list for a
//     * bin.  Bins are converted to trees when adding an element to a
//     * bin with at least this many nodes. The value must be greater
//     * than 2 and should be at least 8 to mesh with assumptions in
//     * tree removal about conversion back to plain bins upon
//     * shrinkage.
//     */
//    //当某一个桶中链表的长度>=8时，链表结构会转换成红黑树结构（其实还要求桶的中数量>=64,后面会提到）
//    static final int TREEIFY_THRESHOLD = 8;
//
//    /**
//     * The bin count threshold for untreeifying a (split) bin during a
//     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
//     * most 6 to mesh with shrinkage detection under removal.
//     */
//    //当红黑树中的节点数量<=6时，红黑树结构会转变为链表结构
//    static final int UNTREEIFY_THRESHOLD = 6;
//
//    /**
//     * The smallest table capacity for which bins may be treeified.
//     * (Otherwise the table is resized if too many nodes in a bin.)
//     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
//     * between resizing and treeification thresholds.
//     */
//    //上面提到的：当Node数组容量>=64的前提下，如果某一个桶中链表长度>=8，则会将链表结构转换成
//    //红黑树结构
//    static final int MIN_TREEIFY_CAPACITY = 64;
//
//
//    /**
//     * The table, initialized on first use, and resized as
//     * necessary. When allocated, length is always a power of two.
//     * (We also tolerate length zero in some operations to allow
//     * bootstrapping mechanics that are currently not needed.)
//     */
//    //我们往map中put的(k,v)都被封装在Node中，所有的Node都存放在table数组中
//    transient Node<K,V>[] table;
//
//    /**
//     * Holds cached entrySet(). Note that AbstractMap fields are used
//     * for keySet() and values().
//     */
//    //用于返回keySet和values
//    transient Set<Map.Entry<K,V>> entrySet;
//
//    /**
//     * The number of key-value mappings contained in this map.
//     */
//    //保存map当前有多少个元素
//    transient int size;
//
//    /**
//     * The number of times this HashMap has been structurally modified
//     * Structural modifications are those that change the number of mappings in
//     * the HashMap or otherwise modify its internal structure (e.g.,
//     * rehash).  This field is used to make iterators on Collection-views of
//     * the HashMap fail-fast.  (See ConcurrentModificationException).
//     */
//    //failFast机制，在讲解ArrayList和LinkedList一文中已经讲过了
//    transient int modCount;
//
//    /**
//     * The next size value at which to resize (capacity * load factor).
//     *
//     * @serial
//     */
//    // (The javadoc description is true upon serialization.
//    // Additionally, if the table array has not been allocated, this
//    // field holds the initial array capacity, or zero signifying
//    // DEFAULT_INITIAL_CAPACITY.)
//
//    //这也是比较重要的一个属性：
//    //创建HashMap时，改变量的值是：初始容量（2的整数次幂）
//    //之后threshold的值是HashMap扩容的门限值：即当前Node/table数组的长度*loadfactor
//
//    //举个例子而言，如果我们传给HashMap构造器的容量大小为9，那么threshold初始值为16，在向HashMap中
//    //put第一个元素后，内部会创建长度为16的Node数组，并且threshold的值更新为16*0.75=12。
//    //具体而言，当我们一直往HashMap put元素的时候，，如果put某个元素后，Node数组中元素个数为13了
//    //，此时会触发扩容（因为数组中元素个数>threshold了，即13>threshold=12），具体扩容操作之后会
//    //详细分析，简单理解就是，扩容操作将Node数组长度*2；并且将原来的所有元素迁移到新的Node数组中。
//    int threshold;
//
//    /**
//     * The load factor for the hash table.
//     *
//     * @serial
//     */
//    //负载因子，见上面对DEFAULT_LOAD_FACTOR 参数的讲解，默认值是0.75
//    final float loadFactor;
//
//
//    //构造器：指定map的大小，和loadfactor
//    public HashMap(int initialCapacity, float loadFactor) {
//        if (initialCapacity < 0)
//            throw new IllegalArgumentException("Illegal initial capacity: " +
//                    initialCapacity);
//        if (initialCapacity > MAXIMUM_CAPACITY)
//            initialCapacity = MAXIMUM_CAPACITY;
//        if (loadFactor <= 0 || Float.isNaN(loadFactor))
//            throw new IllegalArgumentException("Illegal load factor: " +
//                    loadFactor);
//        this.loadFactor = loadFactor;//保存loadfactor
//
//        //注意，前面有讲tableSizeFor函数，该函数返回值：>=initialCapacity、返回值是2的整数次幂
//        //并且得是满足上面两个条件的所有数值中最小的那个数
//        this.threshold = tableSizeFor(initialCapacity);
//    }
//
//    //只指定HashMap容量的构造器，loadfactor使用的是默认的值:0.75
//    public HashMap(int initialCapacity) {
//        this(initialCapacity, DEFAULT_LOAD_FACTOR);
//    }
//
//    //无参构造器，默认loadfactor：0.75
//    public HashMap() {
//        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
//    }
//
////其他不常用的构造器就不分析了
//
//
//
//    //入口,返回对应的value
//    public V get(Object key) {
//        Node<K,V> e;
//
//        //hash：这个函数上面分析过了。返回key混淆后的hashCode
//        //注意getNode返回的类型是Node：当返回值为null时表示map中没有对应的key，注意区分value为
//        //null：如果key对应的value为null的话，体现在getNode的返回值e.value为null，此时返回值也是
//        //null，也就是HashMap的get函数不能判断map中是否有对应的key：get返回值为null时，可能不包
//        //含该key，也可能该key的value为null！那么如何判断map中是否包含某个key呢？见下面contains
//        //函数分析
//        return (e = getNode(hash(key), key)) == null ? null : e.value;
//    }
//
//    public boolean containsKey(Object key) {
//        //注意与get函数区分，我们往map中put的所有的<key,value>都被封装在Node中，如果Node都不存在
//        //显然一定不包含对应的key
//        return getNode(hash(key), key) != null;
//    }
//
//
//
//    //下面分析getNode函数
//    /**
//     * Implements Map.get and related methods
//     *
//     * @param hash hash for key //下面简称：key的hash值
//     * @param key the key
//     * @return the node, or null if none
//     */
//    final Node<K,V> getNode(int hash, Object key) {
//        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//        //(n-1)&hash：当前key可能在的桶索引，put操作时也是将Node存放在index=(n-1)&hash位置
//        //主要逻辑：如果table[index]处节点的key就是要找的key则直接返回该节点；
//        //否则：如果在table[index]位置进行搜索，搜索是否存在目标key的Node：这里的搜索又分两种：
//        //链表搜索和红黑树搜索，具体红黑树的查找就不展开了，红黑树是一种弱平衡(相对于AVL)BST，
//        //红黑树查找、删除、插入等操作都能够保证在O(lon(n))时间复杂度内完成，红黑树原理不在本文
//        //范围内，但是大家要知道红黑树的各种操作是可以实现的，简单点可以把红黑树理解为BST，
//        //BST的查找、插入、删除等操作的实现在之前的博客中有java实现代码，实际上红黑树就是一种平
//        //衡的BST
//        if ((tab = table) != null && (n = tab.length) > 0 &&
//                (first = tab[(n - 1) & hash]) != null) {
//            if (first.hash == hash && // always check first node
//                    ((k = first.key) == key || (key != null && key.equals(k))))
//                return first;//一次就匹配到了，直接返回，否则进行搜索
//            if ((e = first.next) != null) {
//                if (first instanceof TreeNode)
//                    //红黑树搜索/查找
//                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//                do {
//                    //链表搜索(查找)
//                    if (e.hash == hash &&
//                            ((k = e.key) == key || (key != null && key.equals(k))))
//                        return e;//找到了就返回
//                } while ((e = e.next) != null);
//            }
//        }
//        return null;//没找到，返回null
//    }
//
//    //put函数入口,两个参数：key和value
//    public V put(K key, V value) {
//        //下面分析这个函数，注意前3个参数，后面2个参数这里不太重要，因为所有的put后面2个参数都一样
//        return putVal(hash(key), key, value, false, true);
//    }
//
//
//
////下面是put函数的核心处理函数
//    /**
//     * Implements Map.put and related methods
//     *
//     * @param hash hash for key
//     * @param key the key
//     * @param value the value to put
//     * @param onlyIfAbsent if true, don't change existing value
//     * @param evict if false, the table is in creation mode.
//     * @return previous value, or null if none
//     */
//
//    //hash：key的hashCode
//    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
//                   boolean evict) {
//        Node<K,V>[] tab; Node<K,V> p; int n, i;
//        //上面提到过HashMap是懒加载，所有put的时候要先检查table数组是否已经初始化了，
//        //没有初始化得先初始化table数组，保证table数组一定初始化了
//        if ((tab = table) == null || (n = tab.length) == 0)
//            n = (tab = resize()).length;//这个函数后面有resize函数分析
//
//        //到这里表示table数组一定初始化了
//        //与上面get函数相同，指定key的Node，put在table数组的i=(n-1)&hash下标位置，get的时候
//        //也是从table数组的该位置搜索
//        if ((p = tab[i = (n - 1) & hash]) == null)
//            //如果i位置还没有存储元素，则把当前的key，value封装为Node，存储在table[i]位置
//            tab[i] = new Node(hash, key, value, null);
//        else {
//            //如果table[i]位置已经有元素了，则接下来执行的是：
//            //首先判断链表或者二叉树中时候已经存在key的键值对，存在的话就更新它的value
//            //不存在的话把当前的key，value插入到链表的末尾或者插入到红黑树中
//            //如果链表或者红黑树中已经存在Node.key等于key，则e指向该Node，即
//            //e指向一个Node：该Node的key属性与put时传入的key参数相等的那个Node，后面会更新e.value
//            Node<K,V> e; K k;
//
//            //为什么get和put先判断p.hash==hash,下面的if条件中去掉hash的比较也可以逻辑也正确？
//            //因为hash的比较是两个整数的比较，比较的代价相对较小，key是泛型，对象的比较比整数比较
//            //代价大，所以先比较hash，hash相等在比较key
//            if (p.hash == hash &&//
//                    ((k = p.key) == key || (key != null && key.equals(k))))
//                e = p;//e指向一个Node：该Node的key属性与put时传入的key参数相等的那个Node
//            else if (p instanceof TreeNode)
//                //红黑树的插入操作，如果已经存在该key的TreeNode，则返回该TreeNode，否则返回null
//                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//            else {
//                //table[i]处存放的是链表，接下来和TreeNode类似
//                //在遍历链表过程中先判断是key先前是否存在，如果存在则e指向该Node
//                //否则将该Node插入到链表末尾，插入后判断链表长度是否>=8，是的话要进行额外操作
//
//                //binCountt最后的值是链表的长度
//                for (int binCount = 0; ; ++binCount) {
//
//                    if ((e = p.next) == null) {
//                        //遍历到了链表最后一个元素,接下来执行链表的插入操作，先封装为Node再插入
//                        //p指向的是链表最后一个节点，将待插入的Node置为p.next，就完成了单链表的插入
//                        p.next = new Node(hash, key, value, null);
//
//
//                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//                            //TREEIFY_THRESHOLD值是8， binCount>=7,然后又插入了一个新节点，
//                            //链表长度>=8，这时要么进行扩容操作，要么把链表结构转为红黑树结构
//                            //我们接下会分析treeifyBin的源码实现
//                            treeifyBin(tab, hash);
//                        break;
//                    }
//
//                    //当p不是指向链表末尾的时候：先判断p.key是否等于key，等于的话表示当前key
//                    //已经存在了，令e指向p，停止遍历，最后会更新e的value；
//                    //不等的话准备下次遍历，令p=p.next，即p=e
//                    if (e.hash == hash &&
//                            ((k = e.key) == key || (key != null && key.equals(k))))
//                        break;
//                    p = e;
//                }
//            }
//
//
//            if (e != null) { // existing mapping for key
//                //表示当前的key之前已经存在了，并且上面的逻辑保证:e.key一定等于key
//                //这是更新e.value就好
//                V oldValue = e.value;//保存oldvalue
//
//                //onlyIfAbsent默是false，evict为true
//                //onlyIfAbsent为true表示如果之前已经存在key这个键值对了，那么后面在put这个key
//                //时，忽略这个操作，不更新先前的value，这里连接就好
//                if (!onlyIfAbsent || oldValue == null)
//                    e.value = value;//更新e.value
//
//                //这个函数的默认实现是“空”，即这个函数默认什么操作都不执行，那为什么要有它呢？
//                //这是个hook/钩子函数，主要要在LinkedHashMap中，LinkedHashMap重写了这个函数
//                //后面讲解LinkedHashMap时会详细讲解
//                afterNodeAccess(e);
//                return oldValue;//返回旧的value
//            }
//        }
//
//        //如果是第一次插入key这个键，就会执行到这里
//        ++modCount;//failFast机制
//
//        //size保存的是当前HashMap中保存了多少个键值对，HashMap的size方法就是直接返回size
//        //之前说过，threshold保存的是当前table数组长度*loadfactor，如果table数组中存储的
//        //Node数量大于threshold，这时候会进行扩容，即将table数组的容量翻倍。后面会详细讲解
//        //resize方法
//        if (++size > threshold)
//            resize();
//
//        //这也是一个hook函数，作用和afterNodeAccess一样
//        afterNodeInsertion(evict);
//        return null;
//    }
//
//
//    //将链表转换为红黑树结构，在链表的插入操作后调用
//    /**
//     * Replaces all linked nodes in bin at index for given hash unless
//     * table is too small, in which case resizes instead.
//     */
//    final void treeifyBin(Node<K,V>[] tab, int hash) {
//        int n, index; Node<K,V> e;
//
//        //MIN_TREEIFY_CAPACITY值是64,也就是当链表长度>8的时候，有两种情况：
//        //如果table数组的长度<64,此时进行扩容操作
//        //如果table数组的长度>64，此时进行链表转红黑树结构的操作
//        //具体转细节在面试中几乎没有问的，这里不细讲了，
//        //大部同学认为链表长度>8一定会转换成红黑树，这是不对的！！！
//        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
//            resize();
//        else if ((e = tab[index = (n - 1) & hash]) != null) {
//            TreeNode<K,V> hd = null, tl = null;
//            do {
//                TreeNode<K,V> p = replacementTreeNode(e, null);
//                if (tl == null)
//                    hd = p;
//                else {
//                    p.prev = tl;
//                    tl.next = p;
//                }
//                tl = p;
//            } while ((e = e.next) != null);
//            if ((tab[index] = hd) != null)
//                hd.treeify(tab);
//        }
//    }
//
//    /**
//     *    有两种情况会调用当前函数：
//     *    1.之前说过HashMap是懒加载，第一次hHashMap的put方法的时候table还没初始化，
//     * 这个时候会执行resize，进行table数组的初始化，table数组的初始容量保存在threshold中（如果从构造器
//     * 中传入的一个初始容量的话），如果创建HashMap的时候没有指定容量，那么table数组的初始容
//     * 量是默认值：16。即，初始化table数组的时候会执行resize函数
//     *    2.扩容的时候会执行resize函数，当size的值>threshold的时候会触发扩容，即执行resize方法，
//     * 这时table数组的大小会翻倍。
//     *
//     *   注意我们每次扩容之后容量都是翻倍（*2），所以HashMap的容量一定是2的整数次幂，那么HashMap的
//     * 容量为什么一定得是2的整数次幂呢？（面试重点） 要知道原因，首先回顾我们put
//     * key的时候，每一个key会对应到一个桶里面，桶的索引是这样计算的： index = hash & (n-1)，
//     * index的计算最为直观的想法是：hash%n，即通过取余的方式把当前的key、value键值对散列到各个桶中；
//     * 那么这里为什么不用取余(%)的方式呢？原因是CPU对位运算支持较好，即位运算速度很快。
//     *   另外,当n是2的整数次幂时：hash&(n-1)与hash%(n-1)是等价的，但是两者效率来讲是不同的，位运算的效率
//     * 远高于%运算。基于这一点，HashMap中使用的是hash&(n-1)。这还带来了一个好处，就是将旧数组中的Node迁移到扩容后
//     * 的新数组中的时候有一个很方便的特性：HashMap使用table数组保存Node节点，所以table数组扩容的时候（数组扩容）一
//     * 定得是先重新开辟一个数组，然后把就数组中的元素重新散列到新数组中去。这里举一个例子来来说明这个特性：下面以Hash初始容量
//     * n=16，默认loadfactor=0.75举例（其他2的整数次幂的容量也是类似的）：默认容量：n=16，二进制：10000；n-1：15,
//     * n-1二进制：01111，即一连串1。某个时刻，map中元素大于16*0.75=12，即size>12，此时我们新建了一个数组，
//     * 容量为扩容前的两倍，newtab，len=32;接下来我们需要把table中的Node搬移(rehash)到newtab。从table的i=0
//     * 位置开始处理,假设我们当前要处理table数组i索引位置的node，那这个node应该放在newtab的那个位置呢？下面的hash表
//     * 示node.key对应的hash值，也就等于node.hash属性值,另外为了简单，下面的hash只写出了8位（省略的高位的0），实际上
//     * hash是32位：node在newtab中的索引：index=hash%len=hash&(len-1)=hash&(32-1)=hash&31
//     * =hash&(0x0001_1111)；再看node在table数组中的索引计算：i=hash&(16-1)=hash&15
//     * =hash&(0x0000_1111)。注意观察两者的异同：i=hash&(0x0000_1111);index=hash&(0x0001_1111)
//     * 这个表达式有个特点：index=hash&(0x0001_1111)=hash&(0x0000_1111) |
//     * hash&(0x0001_0000) =hash&(0x0000_1111) | hash&n)=i+( hash&n)。什么意思呢：
//     * hash&n要么等于n要么等于0;也就是：inde要么等于i，要么等于i+n;再具体一点：当hash&n==0的时候，index=i;
//     * 当hash&n==n的时候，index=i+n;这有什么用呢？当我们把table[i]位置的所有Node迁移到newtab中去的时候：
//     * 这里面的node要么在newtab的i位置（不变），要么在newtab的i+n位置；也就是我们可以这样处理：把table[i]这个桶中的
//     * node拆分为两个链表l1和类：如果hash&n==0，那么当前这个node被连接到l1链表；否则连接到l2链表。这样下来，
//     * 当遍历完table[i]处的所有node的时候，我们得到两个链表l1和l2，这时我们令newtab[i]=l1,newtab[i+n]=l2,
//     * 这就完成了table[i]位置所有node的迁移/rehash，这也是HashMap中容量一定的是2的整数次幂带来的方便之处。
//     * 下面的resize的逻辑就是上面讲的那样。将table[i]处的Node拆分为两个链表，这两个链表再放到newtab[i]
//     * 和newtab[i+n]位置
//
//     * Initializes or doubles table size.  If null, allocates in
//     * accord with initial capacity target held in field threshold.
//     * Otherwise, because we are using power-of-two expansion, the
//     * elements from each bin must either stay at same index, or move
//     * with a power of two offset in the new table.
//     *
//     * @return the table
//     */
//    final Node<K,V>[] resize() {
//        Node<K,V>[] oldTab = table;//保留扩容前数组引用
//        int oldCap = (oldTab == null) ? 0 : oldTab.length;
//        int oldThr = threshold;
//        int newCap, newThr = 0;
//        if (oldCap > 0) {
//            if (oldCap >= MAXIMUM_CAPACITY) {
//                threshold = Integer.MAX_VALUE;
//                return oldTab;
//            }
//            //正常扩容：newCap = oldCap << 1
//            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
//                    oldCap >= DEFAULT_INITIAL_CAPACITY)
//                //容量翻倍，扩容后的threshold自然也是*2
//                newThr = oldThr << 1; // double threshold
//        }
//        else if (oldThr > 0) // initial capacity was placed in threshold
//            newCap = oldThr;
//        else {               // zero initial threshold signifies using defaults
//            //table数组初始化的时候会进入到这里
//            newCap = DEFAULT_INITIAL_CAPACITY;//默认容量
//            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//threshold
//        }
//        if (newThr == 0) {
//            float ft = (float)newCap * loadFactor;
//            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
//                    (int)ft : Integer.MAX_VALUE);
//        }
//        threshold = newThr;//更新threshold
//        @SuppressWarnings({"rawtypes","unchecked"})
//        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//扩容后的新数组
//        table = newTab;//执行容量翻倍的新数组
//        if (oldTab != null) {
//            for (int j = 0; j < oldCap; ++j) {//之后完成oldTab中Node迁移到table中去
//                Node<K,V> e;
//                if ((e = oldTab[j]) != null) {
//                    oldTab[j] = null;
//                    if (e.next == null)//j这个桶位置只有一个元素，直接rehash到table数组
//                        newTab[e.hash & (newCap - 1)] = e;
//                    else if (e instanceof TreeNode)
//                        //如果是红黑树：也是将红黑树拆分为两个链表，这里主要看链表的拆分，两者逻辑一样
//                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//                    else { // preserve order
//                        //链表的拆分
//                        Node<K,V> loHead = null, loTail = null;//第一个链表l1
//                        Node<K,V> hiHead = null, hiTail = null;//第二个链表l2
//                        Node<K,V> next;
//                        do {
//                            next = e.next;
//                            if ((e.hash & oldCap) == 0) {//rehash到table[j]位置
//                                //将当前node连接到l1上
//                                if (loTail == null)
//                                    loHead = e;
//                                else
//                                    loTail.next = e;
//                                loTail = e;
//                            }
//                            else {
//                                //将当前node连接到l2上
//                                if (hiTail == null)
//                                    hiHead = e;
//                                else
//                                    hiTail.next = e;
//                                hiTail = e;
//                            }
//                        } while ((e = next) != null);
//
//                        if (loTail != null) {
//                            //l1放到table[j]位置
//                            loTail.next = null;
//                            newTab[j] = loHead;
//                        }
//                        if (hiTail != null) {
//                            //l1放到table[j+oldCap]位置
//                            hiTail.next = null;
//                            newTab[j + oldCap] = hiHead;
//                        }
//                    }
//                }
//            }
//        }
//        return newTab;
//    }
//
//
//
//
//
//
//    @Override
//    public Set<Entry<K, V>> entrySet() {
//        return null;
//    }
//
//    /**
//     * HashMap允许key为null，null的hash为0（也意味着HashMap允许key为null的键值对），
//     * 非null的key的hash高16位和低16位分别由由：key的hashCode
//     * 高16位和hashCode的高16位异或hashCode的低16位组成。主要是为了增强hash的随机性减少hash&(n-1)的
//     * 随机性，即减小hash冲突，提高HashMap的性能。所以作为HashMap的key的hashCode函数的实现对HashMap
//     * 的性能影响较大，极端情况下：所有key的hashCode都相同，这是HashMap的性能很糟糕！
//     */
//    static final int hash(Object key) {
//        int h;
//        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//    }
//
//
//    /**
//     * 在new HashMap的时候，如果我们传入了大小参数，这是HashMap会对我们传入的HashMap容量进行传到
//     * tableSizeFor函数处理：这个函数主要功能是：返回一个数：这个数是大于等于cap并且是2的整数次幂
//     * 的所有数中最小的那个，即返回一个最接近cap(>=cap)，并且是2的整数次幂的那个数。
//     * 具体逻辑如下:一个数是2的整数次幂，那么这个数减1的二进制就是一串掩码，即二进制从某位开始是一 串连续的1。
//     */
//    static final int tableSizeFor(int cap) {
//        //举例而言：n的第三位是1(从高位开始数)，
//        int n = cap - 1;
//
//        n |= n >>> 1;
//        n |= n >>> 2;
//        n |= n >>> 4;
//        n |= n >>> 8;
//        n |= n >>> 16;
//        //举例而言：如果n为: 00010000 00000000 00000000 000
//        /*
//        n |= n >>> 1;->n：00011000 00000000 00000000 0000
//        n |= n >>> 2;->n: 00011110 00000000 00000000 0000
//        n |= n >>> 4;->n: 00011111 11100000 00000000 0000
//        n |= n >>> 8;->n: 00011111 11111111 11100000 0000
//        n |= n >>> 16;->n:00011111 11111111 11111111 1111
//
//        返回n+1：00010000 00000000 00000000 000(>=cap并且为2的整数次幂，与cap差值最小的那个)
//        最后的n+1一定是2的整数次幂，并且一定是>=cap
//        整体的思路就是：如果n二进制的第k为1，那么经过上面四个‘|’运算后[0,k]位都变成了1,
//        即：一连串连续的二进制‘1’(掩码)，最后n+1一定是2的整数次幂（如果不溢出）
//        */
//        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
//    }
//
//    //Node是HashMap的内部类
//    static class Node<K, V> implements Map.Entry<K, V> {
//        //保存key的hashcode=key的hashcode ^ (key的hashcode>>>16)这样做主要是为了减少hash冲突
//        //当我们往map中put(k,v)时，这个k,v键值对会被封装为Node，那么这个Node放在Node数组的哪个
//        //位置呢：index=hash&(n-1),n为Node数组的长度。那为什么这样计算hash可以减少冲突呢？如果直接
//        //使用hashCode&(n-1)来计算index，此时hashCode的高位随机特性完全没有用到，因为n相对于
//        //hashcode的值很小。基于这一点，把hashcode 高16位的值通过异或混合到hashCode的低16位，由此
//        //来增强hashCode低16位的随机性
//        final int hash;
//        final K key;//保存map中的key
//        V value;//保存map中的value
//        Node<K, V> next;//单向链表
//
//        Node(int hash, K key, V value, Node<K, V> next) {//构造器
//            this.hash = hash;
//            this.key = key;
//            this.value = value;
//            this.next = next;
//        }
//
//        @Override
//        public K getKey() {
//            return null;
//        }
//
//        @Override
//        public V getValue() {
//            return null;
//        }
//
//        @Override
//        public V setValue(V value) {
//            return null;
//        }
//    }
//
//}
